﻿// -----------------------------------------------------------------------
// <copyright file="TableRowDataDto.cs" company="JSC Centido">
//  Copyright (c) JSC Centido. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Xml;
using System.Xml.Schema;

namespace Centido.Core.Dto
{
    /// <summary>
    ///   A data tranfer object for data in a table row.
    /// </summary>
    public class TableRowDataDto : DataTransferObject
    {
        /// <summary>
        ///   The name of the xml tag that is used to store this object.
        /// </summary>
        public const string XmlTag = "row";

        /// <summary>
        ///   The current xml schema version.
        /// </summary>
        public const string CurrentXmlSchemaVersion = "1";

        /// <summary>
        ///   The name of the xml attribute to store the row id.
        /// </summary>
        public const string IdAttribute = "id";

        /// <summary>
        ///   The name of the xml attribute to store the row creation date and time.
        /// </summary>
        public const string CreatedOnAttribute = "createdOn";

        /// <summary>
        ///   The name of the xml attribute to store the row creator.
        /// </summary>
        public const string CreatedByAttribute = "createdBy";

        /// <summary>
        ///   The name of the xml attribute to store the row last modification date and time.
        /// </summary>
        public const string UpdatedOnAttribute = "updatedOn";

        /// <summary>
        ///   The name of the xml attribute to store the row last modificator.
        /// </summary>
        public const string UpdatedByAttribute = "updatedBy";

        /// <summary>
        ///   The name of the xml attribute to store the row deletion date and time.
        /// </summary>
        public const string DeletedOnAttribute = "deletedOn";

        /// <summary>
        ///   The name of the xml attribute to store the user that deleted the row.
        /// </summary>
        public const string DeletedByAttribute = "deletedBy";

        /// <summary>
        ///   The name of the xml attribute to store the row version.
        /// </summary>
        public const string VersionAttribute = "version";

        /// <summary>
        ///   The name of the xml attribute to store the row owner.
        /// </summary>
        public const string OwnerAttribute = "owner";

        /// <summary>
        ///   The name of the xml tag to store a column value.
        /// </summary>
        public const string ColumnValueTag = "column";

        /// <summary>
        ///   The name of the xml attribute to store the column name.
        /// </summary>
        public const string ColumnNameAttribute = "name";


        /// <summary>
        ///   The column value list.
        /// </summary>
        private readonly Dictionary<string, string> _columnValues
            = new Dictionary<string, string>();


        /// <summary>
        ///   Gets or sets the row id.
        /// </summary>
        public Guid Id
        {
            get;
            set;
        }


        /// <summary>
        ///   Gets or sets the row version.
        /// </summary>
        public Guid? Version
        {
            get;
            set;
        }


        /// <summary>
        ///   Gets or sets the row owner.
        /// </summary>
        public string Owner
        {
            get;
            set;
        }


        /// <summary>
        ///   Gets or sets the row creator.
        /// </summary>
        public string CreatedBy
        {
            get;
            set;
        }


        /// <summary>
        ///   Gets or sets the row creation date and time.
        /// </summary>
        public DateTime? CreatedOn
        {
            get;
            set;
        }


        /// <summary>
        ///   Gets or sets the user that updated the row last.
        /// </summary>
        public string UpdatedBy
        {
            get;
            set;
        }


        /// <summary>
        ///   Gets or sets the row last update date and time.
        /// </summary>
        public DateTime? UpdatedOn
        {
            get;
            set;
        }


        /// <summary>
        ///   Gets or sets the user that deleted the row.
        /// </summary>
        public string DeletedBy
        {
            get;
            set;
        }


        /// <summary>
        ///   Gets or sets the row deletion date and time.
        /// </summary>
        public DateTime? DeletedOn
        {
            get;
            set;
        }



        /// <summary>
        ///   Gets the column values dictionary.
        /// </summary>
        public IDictionary<string, string> ColumnValues
        {
            get
            {
                return _columnValues;
            }
        }


        /// <summary>
        ///   Adds the specified column value to the column value list.
        /// </summary>
        /// <param name="columnName">The column name.</param>
        /// <param name="columnValue">The column value string representation.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="columnName"/> is null.</exception>
        public void AddColumnValue(string columnName, string columnValue)
        {
            if (columnName == null)
                throw new ArgumentNullException("columnName");

            if (!_columnValues.ContainsKey(columnName))
                _columnValues.Add(columnName, columnValue);
            else
                _columnValues[columnName] = columnValue;
        }


        /// <summary>
        ///   Deletes the specified column value from the column value list.
        /// </summary>
        /// <param name="columnName">The column name.</param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="columnName"/> is null.</exception>
        public void DeleteColumnValue(string columnName)
        {
            if (columnName == null)
                throw new ArgumentNullException("columnName");

            if (_columnValues.ContainsKey(columnName))
                _columnValues.Remove(columnName);
        }


        /// <summary>
        ///   This method is reserved and should not be used. When implementing the IXmlSerializable interface, you should return null (Nothing in Visual Basic) from this method, and instead, if specifying a custom schema is required, apply the <see cref="T:System.Xml.Serialization.XmlSchemaProviderAttribute"/> to the class.
        /// </summary>
        /// <returns>
        ///   An <see cref="T:System.Xml.Schema.XmlSchema"/> that describes the XML representation of the object that is produced by the <see cref="M:System.Xml.Serialization.IXmlSerializable.WriteXml(System.Xml.XmlWriter)"/> method and consumed by the <see cref="M:System.Xml.Serialization.IXmlSerializable.ReadXml(System.Xml.XmlReader)"/> method.
        /// </returns>
        public override XmlSchema GetSchema()
        {
            return null;
        }


        /// <summary>
        ///   Generates an object from its XML representation.
        /// </summary>
        /// <param name="reader">
        ///   The <see cref="T:System.Xml.XmlReader"/> stream from which the object is deserialized. 
        /// </param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="reader"/> is null.</exception>
        public override void ReadXml(XmlReader reader)
        {
            if (reader == null)
                throw new ArgumentNullException("reader");

             reader.MoveToContent();

            // Reading the xml schema version
            var xmlSchemaVersion = ReadStringAttribute(reader, XmlSchemaVersionTag, isRequired: false);

            // If the xml schema version is not defined
            if (string.IsNullOrEmpty(xmlSchemaVersion)
                // or if it is the current xml schema version
                || xmlSchemaVersion == CurrentXmlSchemaVersion)
            {
                // Reading the row id
                Id = ReadGuidAttribute(reader, IdAttribute).GetValueOrDefault();

                // Reading the row owner
                Owner = ReadStringAttribute(reader, OwnerAttribute, isRequired: false);

                // Reading the row version
                Version = ReadGuidAttribute(reader, VersionAttribute, isRequired: false);

                // Reading the row creator
                CreatedBy = ReadStringAttribute(reader, CreatedByAttribute, isRequired: false);

                // Reading the row creation date and time                
                CreatedOn = ReadDateTimeAttribute(reader, CreatedOnAttribute, isRequired: false);

                // Reading the user that updated the row last
                UpdatedBy = ReadStringAttribute(reader, UpdatedByAttribute, isRequired: false);

                // Reading the row last update date and time
                UpdatedOn = ReadDateTimeAttribute(reader, UpdatedOnAttribute, isRequired: false);

                // Reading the user that deleted the row
                DeletedBy = ReadStringAttribute(reader, DeletedByAttribute, isRequired: false);

                // Reading the row deletion date and time
                DeletedOn = ReadDateTimeAttribute(reader, DeletedOnAttribute, isRequired: false);

                // Clearing the column value list
                _columnValues.Clear();

                var isEmptyElement = reader.IsEmptyElement;

                reader.ReadStartElement();

                if (!isEmptyElement)
                {
                    // Reading the column values
                    while (reader.LocalName == ColumnValueTag)
                    {
                        // The column name
                        var columnName = ReadStringAttribute(reader, ColumnNameAttribute);

                        isEmptyElement = reader.IsEmptyElement;

                        reader.ReadStartElement();

                        if (!isEmptyElement)
                        {
                            // The column value string representation
                            var columnValue = reader.ReadContentAsString();

                            AddColumnValue(columnName, columnValue);

                            reader.ReadEndElement();
                        }
                        // If the column value tag is empty
                        else
                            // ... then the column value is null
                            AddColumnValue(columnName, null);
                    }

                    reader.ReadEndElement();
                }
            }
            else
                throw new ArgumentException(string.Format(
                    "The specified xml schema version is not supported (node = {0}, version = {1}).",
                    reader.Name,
                    xmlSchemaVersion));
        }


        /// <summary>
        ///   Converts an object into its XML representation.
        /// </summary>
        /// <param name="writer">
        ///   The <see cref="T:System.Xml.XmlWriter"/> stream to which the object is serialized. 
        /// </param>
        /// <exception cref="ArgumentNullException">Thrown when the value of <paramref name="writer"/> is null.</exception>
        public override void WriteXml(XmlWriter writer)
        {
            if (writer == null)
                throw new ArgumentNullException("writer");

            writer.WriteStartElement(XmlTag);

            // The xml schema version
            writer.WriteAttributeString(XmlSchemaVersionTag, CurrentXmlSchemaVersion);

            // The row id
            writer.WriteAttributeString(IdAttribute, Id.ToString());

            // The row owner
            if (!string.IsNullOrEmpty(Owner))
                writer.WriteAttributeString(OwnerAttribute, Owner);

            // The row version
            if (Version.HasValue)
                writer.WriteAttributeString(VersionAttribute, Version.ToString());

            // The row creation date and time
            if (CreatedOn.HasValue)
                writer.WriteAttributeString(CreatedOnAttribute,
                    CreatedOn.Value.ToString(CultureInfo.InvariantCulture));

            // The row creator
            if (!string.IsNullOrEmpty(CreatedBy))
                writer.WriteAttributeString(CreatedByAttribute, CreatedBy);

            // The row last update date and time
            if (UpdatedOn.HasValue)
                writer.WriteAttributeString(UpdatedOnAttribute,
                    UpdatedOn.Value.ToString(CultureInfo.InvariantCulture));

            // The user that updated the row last
            if (!string.IsNullOrEmpty(UpdatedBy))
                writer.WriteAttributeString(UpdatedByAttribute, UpdatedBy);

            // The row deletion date and time
            if (DeletedOn.HasValue)
                writer.WriteAttributeString(DeletedOnAttribute,
                    DeletedOn.Value.ToString(CultureInfo.InvariantCulture));

            // The user that deleted the row
            if (!string.IsNullOrEmpty(DeletedBy))
                writer.WriteAttributeString(DeletedByAttribute, DeletedBy);

            // If there are column values
            if (_columnValues.Count > 0)
            {
                foreach (var columnValue in _columnValues)
                {
                    writer.WriteStartElement(ColumnValueTag);

                    // The column name
                    writer.WriteAttributeString(ColumnNameAttribute, columnValue.Key);

                    if (columnValue.Value != null)
                    {
                        // The column value
                        writer.WriteString(columnValue.Value);

                        // The tag should not be empty even if the value is an empty string
                        writer.WriteFullEndElement();
                    }
                    // If the column value is null
                    else
                        // ... then the tag should be empty
                        writer.WriteEndElement();
                }
            }

            writer.WriteEndElement();
        }
    }
}
